<template>
  <!-- 本体部分 -->
  <div
      :id="id"
      ref="captchaRef"
      :class="['vue-lida-code', { show_: show }]"
      @mousedown="onCloseMouseDown"
      @mouseup="onCloseMouseUp"
      @touchstart="onCloseMouseDown"
      @touchend="onCloseMouseUp"
  >
    <div class="vue-auth-box_" :style="{'border-radius':borderRadius+'px'}" @mousedown.stop @touchstart.stop>
      <div class="auth-body_" :style="`height: ${canvasHeight}px`">
        <!-- 主图，有缺口 -->
        <img ref="img1" :src="state.image1" alt="" :width="canvasWidth" :height="canvasHeight">
        <!-- 小图 -->
        <img
            ref="img2"
            :src="state.image2"
            class="auth-canvas2_"
            :height="canvasHeight"
            :style="`transform:translateX(${styleWidth - sliderBaseSize}px)`"
            alt=""
        >
        <div :class="['loading-box_', { hide_: !state.loading }]">
          <div class="loading-gif_">
            <span/>
            <span/>
            <span/>
            <span/>
            <span/>
          </div>
        </div>
        <div :class="['info-box_', { show: state.infoBoxShow }, { fail: state.infoBoxFail }]">
          {{ state.infoText }}
        </div>
        <div
            :class="['flash_', { show: state.isSuccess }]"
            :style="
            `transform: translateX(${
              state.isSuccess
                ? `${canvasWidth + canvasHeight * 0.578}px`
                : `-${canvasHeight * 0.578}px`
            }) skew(-30deg, 0);`
          "
        />
        <img class="reset_" :src="state.resetSvg" @click="reset">
      </div>
      <div class="auth-control_">
        <div class="range-box" :style="`height:${sliderBaseSize}px`">
          <div class="range-text">{{ sliderText }}</div>
          <div ref="rangeSliderRef" class="range-slider" :style="`width:${styleWidth}px`">
            <div :class="['range-btn', { isDown: state.mouseDown }]" :style="`width:${sliderBaseSize}px`"
                 @mousedown="onRangeMouseDown($event)" @touchstart="onRangeMouseDown($event)">
              <div/>
              <div/>
              <div/>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup name='CustomCaptcha'>
import {} from 'vue'
import {UserService} from "../../api/user/login";

const rangeSliderRef = ref()
const captchaRef = ref()
let props = defineProps({
  id: {type: String, default: undefined},
  canvasWidth: {type: Number, default: 280}, // 主canvas的宽
  canvasHeight: {type: Number, default: 150}, // 主canvas的高
  // 是否出现，由父级控制
  show: {type: Boolean, default: false},
  sliderSize: {type: Number, default: 35}, // 滑块的大小
  failText: {
    type: String,
    default: '验证失败，请重试'
  },
  sliderText: {
    type: String,
    default: '拖动滑块完成拼图'
  },
  borderRadius: {
    type: Number,
    default: 10
  },
});
let emits = defineEmits<{
  (event: 'close'): void
  (event: 'success'): void
  (event: 'fail'): void
}>();

const state = reactive({
  image1: undefined, // 大图
  image2: undefined, // 小图
  mouseDown: false, // 鼠标是否在按钮上按下
  startWidth: 50, // 鼠标点下去时父级的width
  startX: 0, // 鼠标按下时的X
  newX: 0, // 鼠标当前的偏移X
  loading: true, // 是否正在加载中，主要是等图片onload
  isCanSlide: false, // 是否可以拉动滑动条
  error: false, // 图片加在失败会出现这个，提示用户手动刷新
  infoBoxShow: false, // 提示信息是否出现
  infoText: '', // 提示等信息
  infoBoxFail: false, // 是否验证失败
  timer1: undefined, // setTimout1
  closeDown: false, // 为了解决Mac上的click BUG
  isSuccess: false, // 验证成功
  resetSvg: '',
  isSubmit: false, // 是否正在验证中
  startTime: 0,
});
const sliderBaseSize = computed(() => {
  return Math.max(
      Math.min(
          Math.round(props.sliderSize),
          Math.round(props.canvasWidth * 0.5)
      ),
      10
  )
})
const styleWidth = computed(() => {
  const w: number = state.startWidth + state.newX - state.startX
  return w < sliderBaseSize.value
      ? sliderBaseSize
      : w > props.canvasWidth
          ? props.canvasWidth
          : w
})

watch(() => props.show, ((newVal, oldVal) => {
// 每次出现都应该重新初始化
  if (newVal) {
    document.body.classList.add('vue-lida-overflow')
    reset()
  } else {
    document.body.classList.remove('vue-lida-overflow')
  }
}))

onMounted(() => {
  document.body.appendChild(captchaRef.value)
  document.addEventListener('mousemove', onRangeMouseMove, false)
  document.addEventListener('mouseup', onRangeMouseUp, false)
  document.addEventListener('touchmove', onRangeMouseMove, {
    passive: false
  })
  document.addEventListener('touchend', onRangeMouseUp, false)
  if (props.show) {
    document.body.classList.add('vue-lida-overflow')
    reset()
  }
})
/**
 *  capture: Boolean, // 如果是`true`，表示`listener`会在该类型的事件捕获阶段传播到该`EventTarget`时触发
 *  once: Boolean, // 如果是`true`，表示`listener`在添加之后最多只调用一次。`listener`会在其被调用之后自动移除
 *  passive: Boolean, // 默认为true。如果为true，表示`listener`永远不会调用`preventDefault()`。如果`listener`仍然调用了这个函数，客户端将会忽略它并抛出一个控制台警告
 *  所以passive:true的意思就是告诉浏览器，不会阻止默认事件
 */
onBeforeUnmount(() => {
  clearTimeout(state.timer1)
  document.body.removeChild(captchaRef.value)
  document.removeEventListener('mousemove', onRangeMouseMove, false)
  document.removeEventListener('mouseup', onRangeMouseUp, false)
  document.removeEventListener('touchmove', onRangeMouseMove, {
    capture: false
  })
  document.removeEventListener('touchend', onRangeMouseUp, false)
})


// 关闭
const onClose = () => {
  if (!state.mouseDown) {
    clearTimeout(state.timer1)
    emits('close')
  }
}
const onCloseMouseDown = () => {
  state.closeDown = true
}
const onCloseMouseUp = () => {
  if (state.closeDown) {
    onClose()
  }
  state.closeDown = false
}

// 鼠标按下准备拖动
const onRangeMouseDown = (e: MouseEvent) => {
  if (state.isCanSlide) {
    state.startTime = new Date().getTime()
    state.mouseDown = true
    state.newX = e.clientX
    state.startX = e.clientX
  }
}
// 鼠标移动
const onRangeMouseMove = (e: MouseEvent) => {
  if (state.mouseDown) {
    state.newX = e.clientX
  }
}
// 鼠标抬起
const onRangeMouseUp = () => {
  if (state.mouseDown) {
    state.mouseDown = false
    submit()
  }
}
/**
 * 开始进行
 */
const init = () => {
  state.loading = true
  state.isCanSlide = false
  UserService.getCaptcha().then(({data}) => {
    state.image1 = data.image1
    state.image2 = data.image2
    state.loading = false
    state.isCanSlide = true
  })
}
// 开始判定
const submit = () => {
  // 关闭拖动
  state.isCanSlide = false
  state.isSubmit = true
  const x = state.newX - state.startX
  state.loading = true
  UserService.verifyCaptcha({x, width: props.canvasWidth}).then(({data}) => {
    state.isSubmit = false
    state.loading = false
    if (data.success) {
      // 成功
      let useTimeMs: number = new Date().getTime()-state.startTime;
      let useTimeSs: number = (useTimeMs % (1000 * 60)) / 1000
      state.infoText = `验证通过·只花费了${useTimeSs.toFixed(2)}s!`
      state.infoBoxFail = false
      state.infoBoxShow = true
      state.isCanSlide = false
      state.isSuccess = true
      // 成功后准备关闭
      clearTimeout(state.timer1)
      state.timer1 = setTimeout(() => {
        // 成功的回调
        emits('success')
      }, 800)
    } else {
      // 失败
      state.infoText = props.failText
      state.infoBoxFail = true
      state.infoBoxShow = true
      state.isCanSlide = false
      // 失败的回调
      emits('fail')
      // 800ms后重置
      clearTimeout(state.timer1)
      state.timer1 = setTimeout(() => {
        reset()
      }, 800)
    }
  })
}
// 重置
const reset = () => {
  state.infoBoxFail = false
  state.infoBoxShow = false
  state.isCanSlide = true
  state.isSuccess = false
  state.startWidth = sliderBaseSize.value // 鼠标点下去时父级的width
  state.startX = 0 // 鼠标按下时的X
  state.newX = 0 // 鼠标当前的偏移X
  init()
}
</script>

<style scoped lang='less'>
.vue-lida-code {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.3);
  z-index: 999;
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms;

  &.show_ {
    opacity: 1;
    pointer-events: auto;
  }
}

.vue-auth-box_ {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 20px;
  background: #fff;
  user-select: none;
  border-radius: 3px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);

  .auth-body_ {
    position: relative;
    overflow: hidden;
    border-radius: 3px;

    .loading-box_ {
      position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      background-color: rgba(0, 0, 0, 0.8);
      z-index: 20;
      opacity: 1;
      transition: opacity 200ms;
      display: flex;
      align-items: center;
      justify-content: center;

      &.hide_ {
        opacity: 0;
        pointer-events: none;

        .loading-gif_ {
          span {
            animation-play-state: paused;
          }
        }
      }

      .loading-gif_ {
        flex: none;
        height: 5px;
        line-height: 0;
        @keyframes load {
          0% {
            opacity: 1;
            transform: scale(1.3);
          }
          100% {
            opacity: 0.2;
            transform: scale(0.3);
          }
        }

        span {
          display: inline-block;
          width: 5px;
          height: 100%;
          margin-left: 2px;
          border-radius: 50%;
          background-color: #888;
          animation: load 1.04s ease infinite;

          &:nth-child(1) {
            margin-left: 0;
          }

          &:nth-child(2) {
            animation-delay: 0.13s;
          }

          &:nth-child(3) {
            animation-delay: 0.26s;
          }

          &:nth-child(4) {
            animation-delay: 0.39s;
          }

          &:nth-child(5) {
            animation-delay: 0.52s;
          }
        }
      }
    }

    .info-box_ {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 24px;
      line-height: 24px;
      text-align: center;
      overflow: hidden;
      font-size: 13px;
      background-color: #83ce3f;
      opacity: 0;
      transform: translateY(24px);
      transition: all 200ms;
      color: #fff;
      z-index: 10;

      &.show {
        opacity: 0.95;
        transform: translateY(0);
      }

      &.fail {
        background-color: #ce594b;
      }
    }

    .auth-canvas2_ {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 2;
    }

    .auth-canvas3_ {
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0;
      transition: opacity 600ms;
      z-index: 3;

      &.show {
        opacity: 1;
      }
    }

    .flash_ {
      position: absolute;
      top: 0;
      left: 0;
      width: 30px;
      height: 100%;
      background-color: rgba(255, 255, 255, 0.1);
      z-index: 3;

      &.show {
        transition: transform 600ms;
      }
    }

    .reset_ {
      position: absolute;
      top: 2px;
      right: 2px;
      width: 35px;
      height: auto;
      z-index: 12;
      cursor: pointer;
      transition: transform 200ms;
      transform: rotate(0deg);

      &:hover {
        transform: rotate(-90deg);
      }
    }
  }

  .auth-control_ {
    .range-box {
      position: relative;
      width: 100%;
      background-color: #eef1f8;
      margin-top: 20px;
      border-radius: 3px;
      box-shadow: 0 0 8px rgba(240, 240, 240, 0.6) inset;

      .range-text {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 14px;
        color: #b7bcd1;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        text-align: center;
        width: 100%;
      }

      .range-slider {
        position: absolute;
        height: 100%;
        width: 50px;
        background-color: rgba(106, 160, 255, 0.8);
        border-radius: 3px;

        .range-btn {
          position: absolute;
          display: flex;
          align-items: center;
          justify-content: center;
          right: 0;
          width: 50px;
          height: 100%;
          background-color: #fff;
          border-radius: 3px;
          box-shadow: 0 0 4px #ccc;
          cursor: pointer;

          & > div {
            width: 0;
            height: 40%;

            transition: all 200ms;

            &:nth-child(2) {
              margin: 0 4px;
            }

            border: solid 1px #6aa0ff;
          }

          &:hover,
          &.isDown {
            & > div:first-child {
              border: solid 4px transparent;
              height: 0;
              border-right-color: #6aa0ff;
            }

            & > div:nth-child(2) {
              border-width: 3px;
              height: 0;
              border-radius: 3px;
              margin: 0 6px;
              border-right-color: #6aa0ff;
            }

            & > div:nth-child(3) {
              border: solid 4px transparent;
              height: 0;
              border-left-color: #6aa0ff;
            }
          }
        }
      }
    }
  }
}

.vue-lida-overflow {
  overflow: hidden !important;
}
</style>
